<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
        <title>AJ Security 框架-图片验证码</title>
        <meta name="description" content="实用的 Java Web 安全库。图片验证码"/>
        <meta name="keywords" content="security, xss, csrf, captcha, captcha,图片验证码"/>
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <link rel="stylesheet" href="https://framework.ajaxjs.com/static/font/font.css" />
        <link rel="stylesheet" href="/asset/main.css"/>
        <link rel="icon" type="image/x-icon" href="https://framework.ajaxjs.com/aj-logo/logo.ico"/>
        <script src="https://framework.ajaxjs.com/static/aj-docs/common.js"></script>
        <script>
            // 获取用户的默认语言
            var userLang = navigator.language || navigator.userLanguage;

            // 检查是否为中文环境（包括简体和繁体）
            if (userLang.startsWith('zh') && location.pathname.indexOf('cn') == -1) {
                 confirm('欢迎！您可以改为访问中文内容。是否继续？') && location.assign('/cn');  // 如果是中文，则弹出提示
            }

            var _hmt = _hmt || [];
            (function() {
              var hm = document.createElement("script");
              hm.src = "https://hm.baidu.com/hm.js?950ba5ba1f1fe4906c3b4cf836080f03";
              var s = document.getElementsByTagName("script")[0];
              s.parentNode.insertBefore(hm, s);
            })();
        </script>
    </head>
    <body>
        <nav>
            <div>
                <div class="links">
                    <a href="/">🏠 首页</a>
                    | ⚙️ 源码:
                    <a target="_blank" href="https://github.com/lightweight-component/aj-security">Github</a>/<a target="_blank" href="https://gitcode.com/lightweight-component/aj-security">Gitcode</a>
                    |
                    <a href="/">英文版本</a>
                </div>
                <h1><img src="https://framework.ajaxjs.com/aj-logo/logo.png" style="vertical-align: middle;height: 45px;margin-bottom: 6px;" /> AJ Security</h1>
                <h3>用户手册</h3>
            </div>
        </nav>
        <div>
            <menu>
                <ul>
                    <li class="selected">
                        <a href="/cn">首页</a>
                    </li>
                    <li>
                        <a href="/install-cn">安装与配置</a>
                    </li>
                </ul>
                <h3>HTTP Web 安全</h3>
                <ul>
                    <li>
                        <a href="/http/http-referer-cn">HTTP Referer 校验</a>
                    </li>
                    <li>
                       <a href="/http/timestamp-cn">时间戳加密 Token 校验</a>
                    </li>
                     <li>
                       <a href="/http/paramssign-cn">请求参数防篡改</a>
                    </li>
                    <li>
                       <a href="/http/ip-list-cn">IP 白名单/黑名单</a>
                    </li>
                     <li>
                       <a href="/http/nonrepeatsubmit-cn">防止重复提交数据</a>
                    </li>
                </ul>
                <h3>一般性 Web 校验</h3>
                <ul>
                      <li>
                           <a href="/classic/xss-cn">防止 XSS 跨站攻击</a>
                      </li>
                      <li>
                           <a href="/classic/crlf-cn">防止 CRLF 攻击</a>
                      </li>
                </ul>

                <h3>验证码 Captcha 机制</h3>
                <ul>
                    <li><a href="/captcha/img-captcha-cn">图片验证码</a></li>
                    <li><a href="/captcha/google-cn">基于 Google 的验证码</a></li>
                    <li><a href="/captcha/cf-cn">基于 CloudFlare 的验证码</a></li>
                </ul>
                <h3>HTTP 标准认证</h3>
                <ul>
                    <li><a href="/auth/http-basic-auth-cn">HTTP Basic Auth 认证</a></li>
                    <li><a href="/auth/http-digest-auth-cn">HTTP Digest Auth 认证</a></li>
                </ul>
                <h3>API 接口功能</h3>
                <ul>
                    <li><a href="/api/limit-cn">限流限次数</a></li>
                </ul>
                <h3>其他实用功能</h3>
                <ul>
                    <li><a href="/misc/desensitize-cn">实体字段脱敏</a></li>
                    <li><a href="/misc/encryption-api-cn">API 接口加解密</a></li>
                    <li><a href="/misc/trace-id-cn">链路跟踪记录</a></li>
                </ul>
            </menu>
            <article>
                <h1>图片验证码</h1>
<p>图片验证码能够防止机器人注册、灌水等的恶意操作。一般在开放接口进行写入操作的时候需要添加保护的。</p>
<h1>使用方式</h1>
<h2>YAML 配置</h2>
<pre><code class="language-yaml">security:
  ImageCaptcha: # 图片验证码
    enabled: true
    expireSeconds: 60
    message:
      zh_CN:
        a1: 验证码错误
        a2: 参数 [%s] 必填
        a3: 缺少参数 'uuid'
        a4: 不存在图片验证码或者已经过期，请稍后再试
      en_US:
        a1: The captcha code is incorrect.
        a2: The parameter %s is required.
        a3: The parameter 'uuid' is required.
        a4: The captcha code doesn't exist or expired. Please try to generate it again.
</code></pre>
<h2>配置</h2>
<p>创建一个配置类，并添加到 Spring 容器中。主要是决定哪种图片生成器以及缓存方式。这里简单起见使用了 JVM 的缓存 SimpleCache。
如果要改为 Redis 可以参照下面方式来配置<code>SaveToRam</code>、<code>CaptchaCodeFromRam</code>、<code>RemoveByKey</code>。</p>
<pre><code class="language-java">static final SimpleCache RAM = new SimpleCache(); // JVM 缓存

@Bean
ImageCaptchaConfig ImageCaptchaConfig() {
    ImageCaptchaConfig config = new ImageCaptchaConfig();
    config.setCaptchaImageProvider(new SimpleCaptchaImage());
    config.setSaveToRam(RAM::add);
    config.setCaptchaCodeFromRam(key -&gt; {
        SimpleCache.Item item = RAM.get(key);
        return item == null ? null : item.getValue();
    });
    config.setRemoveByKey(RAM::remove);

    return config;
}
</code></pre>
<h2>添加控制器</h2>
<p>首先我们把验证码图片显示出来，添加一个Spring 接口，返回图片验证码：</p>
<pre><code class="language-java">@Autowired
ImageCaptcha imageCaptcha;

@GetMapping(&quot;/captcha&quot;)
void showCaptcha(HttpServletRequest req, HttpServletResponse response) {
    imageCaptcha.captchaImage(req, response);
}
</code></pre>
<p>前端请求该接口须携带一个<code>uuid</code>，表明本次生成验证码的唯一标识。建议使用 QueryString 传参的方式，例如<code>/captcha?uuid=xxx</code>。</p>
<h3>前端引入验证码</h3>
<p>下面用原生 JS 写出前端的代码：</p>
<pre><code class="language-html">&lt;input type=&quot;text&quot; class=&quot;captcha-code&quot; placeholder=&quot;请输入验证码&quot; style=&quot;width:40%&quot; /&gt;
&lt;img src=&quot;http://localhost:8089/base_api/feedback/captcha&quot; class=&quot;captcha-img&quot; title=&quot;单击刷新&quot;
    style=&quot;cursor: pointer;vertical-align: middle;&quot; /&gt;
&lt;script&gt;
    window.CAPTCHA_UUID; // 提交时需要携带的验证码 UUID，QueryString 形式传参，避免与表单实体耦合

    ; (function () {
        const imgApi = 'http://localhost:8089/base_api/feedback/captcha';
        const captchaImg = document.querySelector('.captcha-img');

        function init() {
            window.CAPTCHA_UUID = getUUID();
            captchaImg.src = `${imgApi}?uuid=${window.CAPTCHA_UUID}`;
        }

        init();
        captchaImg.onclick = init;

        function getUUID() {
            if (crypto &amp;&amp; crypto.randomUUID)
                return crypto.randomUUID();
            else return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                const r = (Math.random() * 16) | 0;
                const v = c === 'x' ? r : (r &amp; 0x3) | 0x8;
                return v.toString(16);
            })
        }
    })();
&lt;/script&gt;
</code></pre>
<h3>保护控制器</h3>
<p>在需要保护的接口上添加注解<code>@ImageCaptchaCheck</code>。</p>
<pre><code class="language-java">@PostMapping(&quot;/create_user&quot;)
@ImageCaptchaCheck
boolean createUser(@ModelAttribute User user);
</code></pre>
<p>基本上这样就可以了，拦截器会自动校验验证码，ok 的话走下去业务逻辑，不 ok 抛出异常拦截。</p>

            </article>
        </div>
        <footer>
             AJ Security，开源框架 <a href="https://framework.ajaxjs.com" target="_blank">AJ-Framework</a> 的一部分。联系方式：
             frank@ajaxjs.com，<a href="https://blog.csdn.net/zhangxin09" target="_blank">作者博客</a>
             <br />
             <br />
             Copyright © 2025 Frank Cheung. All rights reserved.
         </footer>
    </body>
</html>